package com.lan.workflow.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.lan.workflow.exception.BaseException;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.javax.el.ExpressionFactory;
import org.activiti.engine.impl.javax.el.ValueExpression;
import org.activiti.engine.impl.juel.ExpressionFactoryImpl;
import org.activiti.engine.impl.juel.SimpleContext;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.ProcessDefinitionImpl;
import org.activiti.engine.impl.pvm.process.ScopeImpl;
import org.activiti.engine.impl.task.TaskDefinition;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class WorkflowUtil {
    private static Logger log = LoggerFactory.getLogger(WorkflowUtil.class);

    private static ObjectMapper objectMapper = new ObjectMapper();

    public static String getCurrentWfUser() {
        return Authentication.getAuthenticatedUserId();
    }

    public static String getBpmn(String modelId, RepositoryService repositoryService) throws Exception {
        Model modelData = repositoryService.getModel(modelId);
        ObjectNode modelNode = (ObjectNode)objectMapper.readTree(repositoryService.getModelEditorSource(modelData.getId()));
        BpmnModel model = (new BpmnJsonConverter()).convertToBpmnModel(modelNode);
        byte[] bpmnBytes = (new BpmnXMLConverter()).convertToXML(model);
        return new String(bpmnBytes, "UTF-8");
    }

    public static String getDefineBpmn(String defindeId, RepositoryService repositoryService) throws Exception {
        BpmnModel model = repositoryService.getBpmnModel(defindeId);
        byte[] bpmnBytes = (new BpmnXMLConverter()).convertToXML(model);
        return new String(bpmnBytes, "UTF-8");
    }

    public static String exportDiagramToFile(RepositoryService repositoryService, ProcessDefinition processDefinition, String exportDir) {
        String diagramResourceName = processDefinition.getDiagramResourceName();
        String key = processDefinition.getKey();
        int version = processDefinition.getVersion();
        String diagramPath = "";

        try {
            InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), diagramResourceName);
            byte[] b = new byte[resourceAsStream.available()];
            int len = 0;
            resourceAsStream.read(b, 0, b.length);
            String diagramDir = exportDir + "/" + key + "/" + version;
            File diagramDirFile = new File(diagramDir);
            if (!diagramDirFile.exists()) {
                diagramDirFile.mkdirs();
            }

            diagramPath = diagramDir + "/" + diagramResourceName;
            File file = new File(diagramPath);
            if (!file.exists()) {
                file.createNewFile();
            }

            FileUtils.writeByteArrayToFile(file, b);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return diagramPath;
    }

    public static ActivityImpl findActivitiImpl(RepositoryService repositoryServiceImpl, String processDefinitionId, String activityId) {
        ProcessDefinition processDefinition = findProcessDefinitionEntityById(repositoryServiceImpl, processDefinitionId);
        ActivityImpl activityImpl = ((ProcessDefinitionImpl)processDefinition).findActivity(activityId);
        return activityImpl;
    }

    public static ActivityImpl findActivitiImpl(RepositoryService repositoryServiceImpl, TaskService taskServiceImpl, String taskId, String activityId) {
        Task task = findTaskById(taskServiceImpl, taskId);
        ProcessDefinition processDefinition = findProcessDefinitionEntityById(repositoryServiceImpl, task.getProcessDefinitionId());
        if (activityId == null) {
            activityId = task.getTaskDefinitionKey();
        }

        if (activityId.toLowerCase().equals("end_node")) {

            Iterator iterator = ((ScopeImpl)processDefinition).getActivities().iterator();

            while(iterator.hasNext()) {
                ActivityImpl activityImpl = (ActivityImpl)iterator.next();
                List<PvmTransition> pvmTransitionList = activityImpl.getOutgoingTransitions();
                if (pvmTransitionList.isEmpty()) {
                    return activityImpl;
                }
            }
        }

        ActivityImpl activityImpl = ((ProcessDefinitionImpl)processDefinition).findActivity(activityId);
        return activityImpl;
    }

    public static ProcessDefinition findProcessDefinitionEntityById(RepositoryService repositoryServiceImpl, String processDefinitionId) {
        ProcessDefinition processDefinition = repositoryServiceImpl.getProcessDefinition(processDefinitionId);
        if (processDefinition == null) {
            throw new RuntimeException("流程定义不存在");
        } else {
            return processDefinition;
        }
    }

    public static ProcessInstance findProcessInstanceById(RuntimeService runtimeServiceImpl, String processInstanceId) {
        ProcessInstance processInstance = (ProcessInstance)runtimeServiceImpl.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            throw new RuntimeException("流程实例id" + processInstanceId + "未找到!");
        } else {
            return processInstance;
        }
    }

    public static ProcessInstance findProcessInstanceByTaskId(RuntimeService runtimeServiceImpl, TaskService taskServiceImpl, String taskId) {
        Task task = findTaskById(taskServiceImpl, taskId);
        String processInstanceId = task.getProcessInstanceId();
        ProcessInstance processInstance = (ProcessInstance)runtimeServiceImpl.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            throw new RuntimeException("流程实例id" + processInstanceId + "未找到!");
        } else {
            return processInstance;
        }
    }

    public static Task findTaskById(TaskService taskServiceImpl, String taskId) {
        Task task = taskServiceImpl.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            log.warn("任务实例{}不存在", taskId);
        }
        return task;
    }

    public static String getDefaultFlowSequence(Task task, RepositoryService rs) {
        String taskActivityId = task.getTaskDefinitionKey();
        ProcessDefinitionEntity def = (ProcessDefinitionEntity)((RepositoryServiceImpl)rs).getDeployedProcessDefinition(task.getProcessDefinitionId());
        ActivityImpl activityImpl = def.findActivity(taskActivityId);
        return activityImpl == null ? null : (String)activityImpl.getProperty("default");
    }

    /**
     * 获取下一个用户任务信息
     * 如果遇到网关，最好提供网关判断需要的参数，不然随便取一条路线，可能不是你需要的
     * @param processInstanceId   流程实例ID
     * @param gatewayParam   排他网关需要用到的参数，不同参数有不同路线
     * @return  下一个用户任务用户组信息
     * @throws Exception
     */
    public static TaskDefinition getNextTaskInfo(String processInstanceId, Map<String,Object> gatewayParam,
                                                 RepositoryService repositoryService, RuntimeService runtimeService) throws Exception {
        ProcessDefinitionEntity processDefinitionEntity = null;
        String id = null;
        TaskDefinition task = null;

        //获取流程实例Id信息
        //String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
        //获取流程发布Id信息
        String definitionId = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult().getProcessDefinitionId();
        processDefinitionEntity = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(definitionId);
        ExecutionEntity execution = (ExecutionEntity) runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        //当前流程节点Id信息
        String activitiId = execution.getActivityId();
        //获取流程所有节点信息
        List<ActivityImpl> activitiList = processDefinitionEntity.getActivities();

        //遍历所有节点信息
        for(ActivityImpl activityImpl : activitiList){
            id = activityImpl.getId();
            if (activitiId.equals(id)) {
                //获取下一个节点信息
                task = nextTaskDefinition(activityImpl, activityImpl.getId(), gatewayParam, processInstanceId, runtimeService);
                break;
            }
        }
        return task;
    }

    /**
     * 下一个任务节点信息,
     *
     * 如果下一个节点为用户任务则直接返回,
     *
     * 根据变量值分别执行排他网关后线路中的el表达式, 并找到el表达式通过的线路后的用户任务
     * @param activityImpl            流程节点信息
     * @param activityId             当前流程节点Id信息
     * @param gatewayParam           排他网关需要用到的参数，不同参数有不同路线
     * @param processInstanceId      流程实例Id信息
     * @return
     */
    private static TaskDefinition nextTaskDefinition(ActivityImpl activityImpl, String activityId,
                                                     Map<String,Object> gatewayParam, String processInstanceId, RuntimeService runtimeService){
        PvmActivity ac = null;
        Object el = null;
        // 如果遍历节点为用户任务并且节点不是当前节点信息
        if ("userTask".equals(activityImpl.getProperty("type")) && !activityId.equals(activityImpl.getId())) {
            // 获取该节点下一个节点信息
            TaskDefinition taskDefinition = ((UserTaskActivityBehavior) activityImpl.getActivityBehavior())
                    .getTaskDefinition();
            return taskDefinition;
        } else if("exclusiveGateway".equals(activityImpl.getProperty("type"))) {// 当前节点为exclusiveGateway
            List<PvmTransition> outTransitions = activityImpl.getOutgoingTransitions();
            // 如果排他网关只有一条线路信息
            if (outTransitions.size() == 1) {
                return nextTaskDefinition((ActivityImpl) outTransitions.get(0).getDestination(), activityId,
                        gatewayParam, processInstanceId, runtimeService);
            } else if (outTransitions.size() > 1) { // 如果排他网关有多条线路信息
                for (PvmTransition tr1 : outTransitions) {
                    el = tr1.getProperty("conditionText"); // 获取排他网关线路判断条件信息
                    // 判断el表达式是否成立
                    if(el == null || gatewayParam == null || gatewayParam.isEmpty()){
                        return nextTaskDefinition((ActivityImpl) tr1.getDestination(), activityId, gatewayParam,
                                processInstanceId, runtimeService);
                    } else if(isCondition(el.toString().trim(), gatewayParam)){
                        return nextTaskDefinition((ActivityImpl) tr1.getDestination(), activityId, gatewayParam,
                                processInstanceId, runtimeService);
                    }
                }
            }
        } else {
            // 获取节点所有流向线路信息
            List<PvmTransition> outTransitions = activityImpl.getOutgoingTransitions();
            List<PvmTransition> outTransitionsTemp = null;
            for (PvmTransition tr : outTransitions) {
                ac = tr.getDestination(); // 获取线路的终点节点
                // 如果流向线路为排他网关
                if ("exclusiveGateway".equals(ac.getProperty("type"))) {
                    outTransitionsTemp = ac.getOutgoingTransitions();
                    // 如果排他网关只有一条线路信息
                    if (outTransitionsTemp.size() == 1) {
                        return nextTaskDefinition((ActivityImpl) outTransitionsTemp.get(0).getDestination(), activityId,
                                gatewayParam, processInstanceId, runtimeService);
                    } else if (outTransitionsTemp.size() > 1) { // 如果排他网关有多条线路信息
                        for (PvmTransition tr1 : outTransitionsTemp) {
                            el = tr1.getProperty("conditionText"); // 获取排他网关线路判断条件信息
                            // 判断el表达式是否成立
                            if(el == null || gatewayParam == null || gatewayParam.isEmpty()){
                                return nextTaskDefinition((ActivityImpl) tr1.getDestination(), activityId, gatewayParam,
                                        processInstanceId, runtimeService);
                            } else if (isCondition(el.toString().trim(), gatewayParam)) {
                                return nextTaskDefinition((ActivityImpl) tr1.getDestination(), activityId, gatewayParam,
                                        processInstanceId, runtimeService);
                            }
                        }
                    }
                } else if ("userTask".equals(ac.getProperty("type"))) {
                    return ((UserTaskActivityBehavior) ((ActivityImpl) ac).getActivityBehavior()).getTaskDefinition();
                }
            }
            return null;
        }
        return null;
    }

    /**
     * 查询流程启动时设置排他网关判断条件信息
     * @param gatewayId          排他网关Id信息, 流程启动时设置网关路线判断条件key为网关Id信息
     * @param processInstanceId  流程实例Id信息
     * @return
     */
    public static String getGatewayCondition(String gatewayId, String processInstanceId, RuntimeService runtimeService) {
        Execution execution = runtimeService.createExecutionQuery().processInstanceId(processInstanceId).singleResult();
        Object object = runtimeService.getVariable(execution.getId(), gatewayId);
        return object == null ? "" : object.toString();
    }

    /**
     * 根据key和value判断el表达式是否通过信息
     * @param el     el表达式信息
     * @param gatewayParam  el表达式 需要用到的参数
     * @return
     */
    public static boolean isCondition(String el, Map<String,Object> gatewayParam) {
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        for(Map.Entry<String,Object> entry : gatewayParam.entrySet()){
            Object val = entry.getValue();
            if (val instanceof Integer) {
                context.setVariable(entry.getKey(), factory.createValueExpression(val, Integer.class));
            } else if (val instanceof Float) {
                context.setVariable(entry.getKey(), factory.createValueExpression(val, Float.class));
            } else if (val instanceof Double) {
                context.setVariable(entry.getKey(), factory.createValueExpression(val, Double.class));
            } else {
                context.setVariable(entry.getKey(), factory.createValueExpression(val, String.class));
            }
        }
        ValueExpression e = factory.createValueExpression(context, el, boolean.class);
        return (Boolean) e.getValue(context);
    }

    /**
     * 检查流程路线是否连续
     * @param model
     */
    public static void checkModel(BpmnModel model) {
        Process process = model.getMainProcess();
        Iterator iterator = process.getFlowElements().iterator();

        while(iterator.hasNext()) {
            FlowElement fe = (FlowElement)iterator.next();
            if (fe != null && fe instanceof SequenceFlow) {
                SequenceFlow sf = (SequenceFlow)fe;
                if (sf.getSourceRef() == null || sf.getSourceRef().length() == 0) {
                    throw new BaseException("500", "流程线 " + sf.getName() + "不存在源");
                }

                if (sf.getTargetRef() == null || sf.getTargetRef().length() == 0) {
                    throw new BaseException("500", "流程线 " + sf.getName() + "不存在目标节点");
                }
            }
        }

    }
}
